package jp.dodododo.janerics;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * 
 * {@.en Utility class for Method.}
 * 
 * <br />
 * 
 * {@.ja Method用のユーティリティクラスです。 }
 * 
 * @author Satoshi Kimura
 */
public abstract class GenericsMethodUtil {
	/**
	 * {@.en Checks if the Method is "implementing method".}
	 * 
	 * <br />
	 * 
	 * {@.ja implメソッドかチェックします。}
	 * 
	 * @param method
	 *            {@.en the Method to check} {@.ja チェック対象のメソッド}
	 * @return {@.en true if the Method is "implementing method"}
	 *         {@.ja implメソッドの場合にtrue}
	 */
	public static boolean isImplMethod(Method method) {
		Class<?> declaringClass = method.getDeclaringClass();
		Class<?>[] parameterTypes = method.getParameterTypes();
		Class<?>[] interfaces = getAllInterfaces(declaringClass);
		for (int i = 0; i < interfaces.length; i++) {
			Class<?> interfaceClass = interfaces[i];
			Method[] methods = interfaceClass.getMethods();
			for (int j = 0; j < methods.length; j++) {
				Method m = methods[j];
				String methodName = method.getName();
				if (m.getName().equals(methodName) == false) {
					continue;
				}
				Type[] genericParameterTypes = m.getGenericParameterTypes();
				if (allMatches(declaringClass, parameterTypes, genericParameterTypes)) {
					return true;
				}
			}
		}
		return false;
	}

	private static Class<?>[] getAllInterfaces(Class<?> declaringClass) {
		List<Class<?>> interfaceList = new ArrayList<Class<?>>();
		addInterface(interfaceList, declaringClass);
		return interfaceList.toArray(new Class[interfaceList.size()]);
	}

	private static void addInterface(List<Class<?>> interfaceList, Class<?> clazz) {
		if (clazz == null) {
			return;
		}
		Class<?>[] interfaces = clazz.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			addInterface(interfaceList, interfaces[i]);
		}
		if (clazz.isInterface() == true) {
			interfaceList.add(clazz);
		} else {
			addInterface(interfaceList, clazz.getSuperclass());
		}
	}

	private static boolean allMatches(Class<?> declaringClass, Class<?>[] parameterTypes, Type[] genericParameterTypes) {
		for (int i = 0; i < genericParameterTypes.length; i++) {
			Type type = genericParameterTypes[i];
			Class<?> actualClass = GenericsTypeUtil.getActualClass(type, declaringClass);
			if (parameterTypes[i].equals(actualClass) == false) {
				return false;
			}
		}
		return true;
	}
}
